package com.googlecode.stemwijzer.rebind;

import java.util.Map;

import com.google.gwt.core.client.GWT;
import com.google.gwt.core.ext.UnableToCompleteException;
import com.google.gwt.core.ext.typeinfo.JClassType;
import com.google.gwt.core.ext.typeinfo.JType;
import com.google.gwt.json.client.JSONObject;
import com.google.gwt.json.client.JSONParser;
import com.google.gwt.json.client.JSONValue;
import com.google.gwt.user.rebind.ClassSourceFileComposerFactory;
import com.googlecode.stemwijzer.rebind.util.GeneratorUtil;
import com.googlecode.stemwijzer.shared.serializer.rest.IsJsonSerializer;
import com.googlecode.stemwijzer.shared.serializer.rest.IsXmlSerializer;
import com.googlecode.stemwijzer.shared.serializer.rest.json.AbstractJsonCodec;

/**
 * The Class RestSerializerGenerator.
 * 
 * @author j.brosens
 */
public class RestSerializerGenerator extends BaseGenerator {

	private JClassType genericTypedInterface;
	private Map<String, JType> properties;

	@Override
	protected void onCompose(final ClassSourceFileComposerFactory composer) {
		composer.addImplementedInterface(this.generatedType.getQualifiedSourceName());
	}

	@Override
	protected void onGenerate() throws UnableToCompleteException {
		this.genericTypedInterface = this.getGenericTypedInterface(this.generatedType);
		this.properties = GeneratorUtil.discoverTypeProperties(this.genericTypedInterface);

		JClassType[] interfaces = this.generatedType.getImplementedInterfaces();
		for (JClassType iface : interfaces) {
			if (iface.getName().equals(typeOracle.findType(IsJsonSerializer.class.getName()).getName())) {
				generateFromJson();
				generateToJson();
			}

			if (iface.getName().equals(typeOracle.findType(IsXmlSerializer.class.getName()).getName())) {
				// Not implemented
			}
		}

	}

	private void generateFromJson() {
		writeOverriddenMethodHeader("fromJson", this.genericTypedInterface.getQualifiedSourceName(), "final "
				+ String.class.getName() + " json");

		srcWriter.println("if (json == null) {");
		srcWriter.indent();
		srcWriter.println("return null;");
		writeCloseCodeBlock();

		srcWriter.println(JSONValue.class.getName() + " val = " + JSONParser.class.getName() + ".parse(json);");
		srcWriter.println(JSONObject.class.getName() + " obj = val.isObject();");
		srcWriter.println("if (obj == null) {");
		srcWriter.indent();
		srcWriter.println("return null;");
		writeCloseCodeBlock();

		srcWriter.println(this.genericTypedInterface.getQualifiedSourceName() + " entity = " + GWT.class.getName()
				+ ".create(" + this.genericTypedInterface.getQualifiedSourceName() + ".class);");

		srcWriter.println(JSONValue.class.getName() + " jsonValue = null;");

		for (String property : properties.keySet()) {
			JType type = properties.get(property);
			JClassType classType = typeOracle.findType(type.getQualifiedSourceName());

			writeComment("property: " + property + ", type: " + type.getSimpleSourceName() + ", class: "
					+ classType.getQualifiedSourceName());

			if (classType.isInterface() != null) {
				final String name = classType.getName();

				srcWriter.println(this.packageName + "." + name + "Serializer " + name.toLowerCase() + "_serializer = "
						+ GWT.class.getName() + ".create(" + this.packageName + "." + name + "Serializer.class);");

				srcWriter.println("jsonValue = obj.get(\"" + property.toLowerCase() + "\");");
				srcWriter.println("if (jsonValue != null) {");
				srcWriter.indent();
				srcWriter.println(classType.getQualifiedSourceName() + " " + name.toLowerCase() + "_entity = "
						+ name.toLowerCase() + "_serializer.fromJson(jsonValue.toString());");
				srcWriter.println("entity.set" + property + "(" + name.toLowerCase() + "_entity);");
				writeCloseCodeBlock();
			} else if (classType.isEnum() != null) {
				// TODO: handle enums, possibly unneeded
			} else {
				String codecIdentifier = AbstractJsonCodec.getIdentifier(classType.getQualifiedSourceName());

				srcWriter.println("jsonValue = obj.get(\"" + property.toLowerCase() + "\");");
				srcWriter.println("if (jsonValue != null) {");
				srcWriter.indent();
				srcWriter.println("entity.set" + property + "(" + AbstractJsonCodec.class.getName() + "."
						+ codecIdentifier + ".decode(jsonValue));");
				writeCloseCodeBlock();
			}
		}
		srcWriter.println("return entity;");

		writeCloseMethod();
	}

	private void generateToJson() {
		writeOverriddenMethodHeader("toJson", String.class.getName(), "final "
				+ this.genericTypedInterface.getQualifiedSourceName() + " entity");

		srcWriter.println(JSONObject.class.getName() + " jsonObject = new " + JSONObject.class.getName() + "();");

		for (String property : properties.keySet()) {
			JType type = properties.get(property);
			JClassType propertyType = typeOracle.findType(type.getQualifiedSourceName());

			writeComment("property: " + property + ", type: " + type.getSimpleSourceName() + ", class: "
					+ propertyType.getQualifiedSourceName());

			if (propertyType.isInterface() != null) {
				// TODO: handle interfaces
				srcWriter.println();
			} else if (propertyType.isEnum() != null) {
				// TODO: handle enums
				srcWriter.println();
			} else {
				String codecIdentifier = AbstractJsonCodec.getIdentifier(propertyType.getQualifiedSourceName());
				srcWriter.println("jsonObject.put(\"" + property.toLowerCase() + "\", entity.get" + property
						+ "() != null ? " + AbstractJsonCodec.class.getName() + "." + codecIdentifier
						+ ".encode(entity.get" + property + "()) : null);");
			}
		}
		srcWriter.println("return jsonObject.toString();");
		writeCloseMethod();
	}
}
